#pragma once
#include <iostream>
#include <string>
#include <memory>
#include "Connection.hpp"
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace LogModule;

#define SIZE 1024

// 普通sockfd的封装
class Channel : public Connection
{
public:
    Channel(int sockfd, const InetAddr &client)
        : _sockfd(sockfd),
          _client_addr(client)
    {
        // 默认将该fd设置为非阻塞
        SetNonBlock(_sockfd);
    }

    // 获取fd的方法
    int GetSockFd() override
    {
        return _sockfd;
    }

    // 读取方法
    // 问题1：怎么保证我把本轮数据读完？ 答：循环读取 --- 本层指解决IO问题
    // 问题2：即便是你把本轮数据读完，你怎么知道数据就有完整的报文，如果不完整、有多个呢
    //       该如何解决粘包问题？序列化反序列化问题？ 答：引入协议
    void Recver() override
    {
        // LOG(LogLevel::DEBUG) << "事件被派发到了Clannel模块";

        // ET加非阻塞fd得循环读取
        char buffer[SIZE];
        while (true)
        {
            buffer[0] = 0;                                            // 清空字符串
            ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0); // 非阻塞
            if (n > 0)
            {
                // 读取成功
                buffer[n] = 0;
                // 放入该fd的接收缓冲区中
                _inbuffer += buffer; // 接收缓冲区中入队列的过程
            }
            else if (n == 0)
            {
                // 表示对方在我们服务端读取过程中主动关闭了连接
                // 我们只能进行异常处理了
                Excepter();
                return;
            }
            else
            {
                // 在非阻塞时n<0可能是把数据读完了，可能是被信号中断了，也可能是读取出错了
                // 我们判断一下就好
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // 此时是把数据读完返回的<0
                    // 那么我们退出循环就好了
                    break;
                }
                else if (errno == EINTR)
                {
                    // 此时是被信号导致的中断返回
                    // 我们继续读取就好
                    continue;
                }
                else
                {
                    // 此时是真正的读取出错了
                    // 那就进行异常处理
                    Excepter();
                    return;
                }
            }
        }

        if (!_inbuffer.empty())
        {
            // 如果接收缓冲区不为空证明是有数据的
            // 那么我们进行回调上层传过来的处理方法进行处理
            // 传入_inbuffer进行处理，处理好报文返回时我们使用_outbuffer接收
            _outbuffer += _handler(_inbuffer);
            LOG(LogLevel::DEBUG) << "_outbuffer: " << _outbuffer;
        }

        // 如果发送缓冲区不为空，我们就直接转到发送逻辑就好
        if (!_outbuffer.empty())
        {
            // Sender();
            // 由于我们epoll设置写事件关心时会默认触发一次写事件就绪
            // 那么就会调用一次Sender进行发送数据，所以我们在这里直接设置关心写事件操作也行
            // 这样我们不用手动调用Sender~
            // GetOwner()->EnableReadWrite(_sockfd, true, true);
            // 但其实最好还是我们自己调用，这样不需要等待下一次判断才能发送数据，不浪费时间
            Sender();
        }
    }

    // 写入方法
    void Sender() override
    {
        while (true)
        {
            ssize_t n = send(_sockfd, _outbuffer.c_str(), _outbuffer.size(), 0);
            if (n > 0)
            {
                // 我们其实期望是把缓冲区中的数据一次性发送完成
                // 但是os不一定会全发完，send返回值就是实际发送的数据
                //  发送了n大小的数据，那么我们就把这些数据从缓冲区中移除
                _outbuffer.erase(0, n);
                // 一直发送直到发送缓冲区为空
                if (_outbuffer.empty())
                    break;
            }
            else if (n == 0)
            {
                // 发送缓冲区空了，停止发送就好
                break;
            }
            else
            {
                // n<0有几种情况，我们根据errno错误码来判断
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                    // 表明写事件不就绪，发送缓冲区满了
                    break;
                else if (errno == EINTR)
                    // 表明写事件被信号中断了，我们重新发送
                    continue;
                else
                {
                    // 表明发送真正出错，我们进行异常处理
                    Excepter();
                    return;
                }
            }
        }

        // 循环结束后有两种情况
        // 1.发送缓冲区为空，发送完毕了
        // 2.内核缓冲区被写满了，发送条件不具备
        if (!_outbuffer.empty())
        {
            // 在循环结束后该fd的发送缓冲区还不为空，就说明内核发送缓冲区已经被写满了
            // 需要设置EPOLLPOUT，开启epoll对写事件的关心
            // 这些剩余的数据在我们epoll等待到写事件就绪时
            // 我们Reactor层已经写好了此时会调用一次Sender,将剩余数据发出
            // 所以不用考虑剩余数据问题
            // 使用回指指针调用Reactor类中的EnableReadWrite方法
            // 读要常设为关心，因此保持为true，我们现在设置要关心写，那就启动它，传true
            GetOwner()->EnableReadWrite(_sockfd, true, true);
        }
        else
        {
            // 证明发送完毕，内核发送缓冲区是有空间的
            // 写事件是就绪的，所以我们不需要让epoll关心写事件啦，那就关闭它，传false
            // 读要常设为关心，因此保持为true
            GetOwner()->EnableReadWrite(_sockfd, true, false);
        }
        // 我们这样写之后手动在Recv中调用一次Sender，然后如果有数据没有发送完，我们可以不管
        // 让epoll进行关心，有写事件就绪就会在Reactor层自动调用Sender直到把剩余数据发完
    }

    // 处理异常的方法
    void Excepter() override
    {
        // 代码中所有的异常，都被我们归到了这个函数中进行处理
        // 此时如果我们代码中有出错的异常部分，我们都可以归结为是这个函数的处理逻辑有问题
        // 就方便我们维护代码！！
        // 那么我们在这里出现异常，我们需要把连接从epoll中移除关心和关闭相应fd
        // 以及从_connections容器中移除该连接
        // 我们统一在Reactor中的连接删除方法中去写，这里调用就好
        GetOwner()->DelConnection(_sockfd);
    }

    ~Channel() {}

private:
    int _sockfd;
    // 使用string充当缓冲区是处理不了图片音频之类的，其实也可以使用vector<char>来充当
    std::string _inbuffer;  // 接收缓冲区
    std::string _outbuffer; // 发送缓冲区

    // client info，用来获取该连接是哪个客户端发起的消息
    InetAddr _client_addr;
};